﻿using System;
using System.Data;
using System.Data.Entity;
using System.Data.Objects;
using System.Data.Entity.Infrastructure;
using System.Collections.ObjectModel;
using System.Linq.Expressions;

namespace Rainr.EFClient.Utilites
{
    /// <summary>
    /// 数据访问工具
    /// </summary>
    public class DbAccessorUtility
    {
        /// <summary>
        /// 一个对象是否为代理对象。
        /// </summary>
        /// <param name="entity">对象</param>
        /// <returns>如果是则返回真。</returns>
        public static bool IsProxyEntity(object entity)
        {
            return entity != null && ObjectContext.GetObjectType(entity.GetType()) != entity.GetType();
        }
        /// <summary>
        /// 一个对象是否为代理对象。
        /// </summary>
        /// <param name="type">对象类型</param>
        /// <returns>如果是则返回真。</returns>
        public static bool IsProxyEntity(Type type)
        {
            return ObjectContext.GetObjectType(type) != type;
        }

        /// <summary>
        /// 将一个非代理对象（POCO）来获取数据访问上下文中已存在的同一键的代理对象。
        /// 如果传入的是一个已代理的对象，则将直接返回此传入的对象。
        /// </summary>
        /// <typeparam name="TEntity">对象类型</typeparam>
        /// <param name="entity">非代理对象（POCO）</param>
        /// <param name="context">数据访问上下文</param>
        /// <returns>数据访问上下文中已代理对象。如果代理对象不存在则返回NULL。</returns>
        public static TEntity GetProxyEntity<TEntity>(TEntity entity, DbContext context) where TEntity : class
        {
            Type type = entity.GetType();
            if (!DbAccessorUtility.IsProxyEntity(type))
            {
                ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
                object originalEntity = null;
                if (!objectContext.TryGetObjectByKey(objectContext.CreateEntityKey(type.Name, entity), out originalEntity))
                {
                    return default(TEntity);
                }
                return originalEntity as TEntity;
            }
            return entity;
        }
        /// <summary>
        /// 获得对应的字段名称。
        /// </summary>
        /// <typeparam name="TSource">对象类型</typeparam>
        /// <typeparam name="TKey">字段类型</typeparam>
        /// <param name="keySelector">字段表达式</param>
        /// <returns>字段名称</returns>
        public static string GetPropertyName<TSource, TKey>(Expression<Func<TSource, TKey>> keySelector)
        {
            string fieldName = null;
            var exp = keySelector.Body as UnaryExpression;
            if (exp == null)
            {
                var body = keySelector.Body as MemberExpression;
                fieldName = body.Member.Name;
            }
            else
            {
                fieldName = (exp.Operand as MemberExpression).Member.Name;
            }
            return fieldName;
        }

        /// <summary>
        /// 将一个对象的值替换到数据访问上下文中已存在的同一键的代理对象，并返回此代理对象。
        /// </summary>
        /// <typeparam name="TEntity">对象类型</typeparam>
        /// <param name="entity">对象</param>
        /// <param name="context">数据访问上下文</param>
        /// <returns>数据访问上下文中已代理对象。如果代理对象不存在则返回NULL。</returns>
        public static TEntity ReplaceEntity<TEntity>(TEntity entity, DbContext context) where TEntity : class
        {
            Type type = entity.GetType();
            ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
            object originalEntity = null;
            if (!objectContext.TryGetObjectByKey(objectContext.CreateEntityKey(type.Name, entity), out originalEntity))
            {
                return default(TEntity);
            }
            return objectContext.ApplyCurrentValues<TEntity>(type.Name, entity);
        }

        /// <summary>
        /// 恢复对象操作的错误状态。
        /// 如果对象在Insert/Update/Delete时发生错误。
        /// 那么调用此方法将会恢复此对象在对象状态管理区的状态。
        /// 避免下次正常的Insert/Update/Delete发生此错误。
        /// </summary>
        /// <param name="dbContext">数据上下文</param>
        public static void RestoreEntityState(DbContext dbContext)
        {
            ObjectContext objectContext = dbContext.GetObjectContext();
            if (null == objectContext) return;
            foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added))
            {
                if (entry.Entity != null) objectContext.DeleteObject(entry.Entity);
            }
            foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
            {
                if (entry.Entity != null) objectContext.Refresh(System.Data.Objects.RefreshMode.StoreWins, entry.Entity);
            }
            foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted))
            {
                if (entry.Entity != null) objectContext.Refresh(System.Data.Objects.RefreshMode.StoreWins, entry.Entity);
            }
            objectContext.AcceptAllChanges();
        }
        /// <summary>
        /// 恢复对象某一类操作的（Insert/Update/Delete）错误状态。
        /// </summary>
        /// <param name="dbContext">数据上下文</param>
        /// <param name="entityState">操作类别（Insert对应Added，Update对应Modified，Delete对应 Deleted）</param>
        public static void RestoreEntityState(DbContext dbContext, EntityState entityState)
        {
            ObjectContext objectContext = dbContext.GetObjectContext();
            if (null == objectContext) return;
            switch (entityState)
            {
                case EntityState.Added:
                    foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(entityState))
                    {
                        if (entry.Entity != null) objectContext.DeleteObject(entry.Entity);
                    }
                    objectContext.AcceptAllChanges();
                    break;
                case EntityState.Deleted:
                    foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted))
                    {
                        if (entry.Entity != null) objectContext.Refresh(System.Data.Objects.RefreshMode.StoreWins, entry.Entity);
                    }
                    objectContext.AcceptAllChanges();
                    break;
                case EntityState.Modified:
                    foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
                    {
                        if (entry.Entity != null) objectContext.Refresh(System.Data.Objects.RefreshMode.StoreWins, entry.Entity);
                    }
                    objectContext.AcceptAllChanges();
                    break;
            }
        }
    }
}
